﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace DeepTalkDBSerice
{
    /// <summary>
    /// Manages render clients. It keeps a list of all known render clients
    /// and makes sure to notify them one-by-one about doing an actual
    /// render.
    /// </summary>
    class ManageRenderClients
    {
        /// <summary>
        /// The internal interface that represents a single client
        /// </summary>
        public interface IRenderClient
        {
            /// <summary>
            /// Render client should return true if the client is allowed to render this
            /// particular object.
            /// </summary>
            /// <param name="id"></param>
            /// <param name="agendaXML"></param>
            /// <returns></returns>
            bool Render(int id, string agendaXML);
        }

        /// <summary>
        /// The list of render clients we know about
        /// </summary>
        private List<IRenderClient> _clients = new List<IRenderClient>();

        public void AddClient(IRenderClient client)
        {
            lock (_clients)
            {
                _clients.Add(client);
            }
        }

        /// <summary>
        /// Remove a client
        /// </summary>
        /// <param name="client"></param>
        public void RemoveClient(IRenderClient client)
        {
            lock (_clients)
            {
                _clients.Remove(client);
            }
        }

        /// <summary>
        /// Submit a background thread to scan our DB for new unrendred talks that we can
        /// get to work on.
        /// </summary>
        internal void ReScanForWork()
        {
            ThreadPool.QueueUserWorkItem((o) => RescanForWorkOnce());
        }

        /// <summary>
        /// Look for an unrendered talk, and if we have one, see if we can't get a client to render it.
        /// </summary>
        private void RescanForWorkOnce()
        {
            ///
            /// First, we have to make sure that we are the only one of these threads that is working right now!
            /// This is crude, and perhaps there is a better way. :-)
            /// 

            lock (_clients)
            {
                ///
                /// Make sure we have someone to do the work!
                /// 

                if (_clients.Count == 0)
                {
                    return;
                }

                ///
                /// Loop, assigning work until no one wants the work, or we are out of work.
                /// 

                bool noWorkCanBeDone = false;
                while (!noWorkCanBeDone)
                {
                    Conference cf = Conference.GetFirstUnRenderedTalkRO();
                    if (cf == null)
                    {
                        noWorkCanBeDone = true;
                        continue;
                    }
                    
                    ///
                    /// Assume that the talk is going to be rendered. We have to do this because of an ordering issue:
                    /// If we ask "AttempARender" and the client is *very* fast, it might mark the DB as rendered
                    /// before we mark it as rendering - so we'll get out of order. So, set it to rendering, and then
                    /// unset it if no one wants to render the thing.
                    /// Don't you love client-server stuff? :-)
                    /// 

                    ManageTalkState.SetStateRendering(cf.Id);

                    ///
                    /// See if we can find someone to do the talk
                    /// 

                    var clients = from c in _clients
                                 where AttemptARender(c, cf.Id, cf.AgendaXML)
                                 select c;
                    var client = clients.FirstOrDefault();

                    ///
                    /// If no one wanted it, then we need to mark is as not rendered.
                    /// 

                    if (client == null)
                    {
                        ManageTalkState.SetStateUnrendered(cf.Id);
                        noWorkCanBeDone = true;
                    }
                    else
                    {
                    }
                }
            }
        }

        /// <summary>
        /// Trys to get a client to render. There is an unfortunate race condition -- the service that does
        /// the rendering may have already disconnected but the Remove may not yet have been called. Thus
        /// the callback will fail. So we have to protect against it...
        /// </summary>
        /// <param name="c"></param>
        /// <param name="p"></param>
        /// <param name="p_3"></param>
        /// <returns></returns>
        private bool AttemptARender(IRenderClient c, int id, string agendaXML)
        {
            try
            {
                return c.Render(id, agendaXML);
            }
            catch (Exception e)
            {
                /// TODO: log this errors
            }
            return false;
        }
    }
}
